﻿using gt.rediscache.core.Entry;
using StackExchange.Redis;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace gt.rediscache.core.Utility
{
    /// <summary>
    /// Redis Info 解析
    /// </summary>
    public sealed class RedisInfoParser
    {
        private static readonly ConcurrentDictionary<string, PropertyInfo> _sectionMappings;
        private static readonly ConcurrentDictionary<Type, Dictionary<string, PropertyInfo>> _propertyMappings;
        static RedisInfoParser()
        {
            _propertyMappings = new ConcurrentDictionary<Type, Dictionary<string, PropertyInfo>>();
            _sectionMappings = new ConcurrentDictionary<string, PropertyInfo>();

            var sections = typeof(RedisInfo).GetProperties().Where(s => typeof(RedisInfoSection).IsAssignableFrom(s.PropertyType));
            foreach (var section in sections)
            {
                _sectionMappings[section.Name] = section;
                var propMaps = _propertyMappings[section.PropertyType] = new Dictionary<string, PropertyInfo>();

                var type = section.PropertyType;
                var props = type.GetProperties().Where(p => p.IsDefined(typeof(RedisInfoPropertyAttribute), false));
                foreach (var prop in props)
                {
                    var propAttribute = prop.GetCustomAttribute<RedisInfoPropertyAttribute>();
                    propMaps[propAttribute.PropertyName] = prop;
                }
            }
        }

        public static RedisInfo FromInfoString(string infoStr)
        {
            var info = new RedisInfo { FullInfoString = infoStr };

            RedisInfoSection currentSection = null;

            var lines = infoStr.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
            foreach (var line in lines)
            {
                if (line.StartsWith("#"))
                {
                    var sectionName = line.Replace("# ", "");
                    PropertyInfo currentSectionProp;
                    if (_sectionMappings.TryGetValue(sectionName, out currentSectionProp))
                    {
                        currentSection = (RedisInfoSection)currentSectionProp.GetValue(info);
                    }
                    else
                    {
                        currentSection = new RedisInfoSection { Name = sectionName, IsUnrecognized = true };
                        if (info.UnrecognizedSections == null)
                            info.UnrecognizedSections = new List<RedisInfoSection>();
                        info.UnrecognizedSections.Add(currentSection);
                    }
                    continue;
                }
                if (currentSection == null)
                {
                    //TODO: Take care of global pre-2.6 case here
                    continue;
                }

                var splits = line.Split(StringSplits.Colon, 2);
                if (splits.Length != 2)
                {
                    currentSection.MapUnrecognizedLine(line);
                    continue;
                }

                string key = splits[0], value = splits[1];
                currentSection.AddLine(key, value);

                if (currentSection.IsUnrecognized)
                    continue;

                PropertyInfo propertyInfo;
                var prop = _propertyMappings[currentSection.GetType()].TryGetValue(key, out propertyInfo) ? propertyInfo : null;
                if (prop == null)
                {
                    currentSection.MapUnrecognizedLine(key, value);
                    continue;
                }

                try
                {
                    if (prop.PropertyType == typeof(bool))
                    {
                        value = string.IsNullOrEmpty(value) ? "0" : value;
                        prop.SetValue(currentSection, value != "0");
                    }
                    else
                    {
                        prop.SetValue(currentSection, Convert.ChangeType(value, prop.PropertyType));
                    }
                }
                catch
                {
                    //throw new Exception($"Error parsing '{value}' from {key} as {prop.PropertyType.Name} for {currentSection.GetType()}.{prop.Name}", e);
                }
            }

            return info;
        }

        public static ConnectClientInfo[] FromClientInfo(ClientInfo[] infoArray)
        {
            if (infoArray == null || infoArray.Length == 0) return null;
            List<ConnectClientInfo> list = new List<ConnectClientInfo>();
            foreach (var c in infoArray)
            {
                list.Add(new ConnectClientInfo()
                {
                    Address = c.Address,
                    AgeSeconds = c.AgeSeconds,
                    Host = c.Host,
                    LastCommand = c.LastCommand,
                    Name = c.Name,
                    Port = c.Port,
                    Raw = c.Raw
                });
            }
            return list.ToArray();
        }
    }
}
